// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2017 HiSilicon Limited, All Rights Reserved.
 * Author: Gabriele Paoloni <gabriele.paoloni@huawei.com>
 * Author: Zhichang Yuan <yuanzhichang@hisilicon.com>
 */

#define pr_fmt(fmt)	"LOGIC PIO: " fmt

#include <linux/of.h>
#include <linux/io.h>
#include <linux/logic_pio.h>
#include <linux/mm.h>
#include <linux/rculist.h>
#include <linux/sizes.h>
#include <linux/slab.h>

/* The unique hardware address list */
static LIST_HEAD(io_range_list);
static DEFINE_MUTEX(io_range_mutex);

/* Consider a kernel general helper for this */
#define in_range(b, first, len)        ((b) >= (first) && (b) < (first) + (len))

/**
 * find_io_range_by_fwnode - find logical PIO range for given FW node
 * @fwnode: FW node handle associated with logical PIO range
 *
 * Returns pointer to node on success, NULL otherwise.
 *
 * Traverse the io_range_list to find the registered node for @fwnode.
 */
struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode)
{
	struct logic_pio_hwaddr *range;

	list_for_each_entry_rcu(range, &io_range_list, list) {
		if (range->fwnode == fwnode)
			return range;
	}
	return NULL;
}

/**
 * logic_pio_trans_hwaddr - translate HW address to logical PIO
 * @fwnode: FW node reference for the host
 * @addr: Host-relative HW address
 * @size: size to translate
 *
 * Returns Logical PIO value if successful, ~0UL otherwise
 */
unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode,
				     resource_size_t addr, resource_size_t size)
{
	struct logic_pio_hwaddr *range;

	range = find_io_range_by_fwnode(fwnode);
	if (!range || range->flags == LOGIC_PIO_CPU_MMIO) {
		pr_err("IO range not found or invalid\n");
		return ~0UL;
	}
	if (range->size < size) {
		pr_err("resource size %pa cannot fit in IO range size %pa\n",
		       &size, &range->size);
		return ~0UL;
	}
	return addr - range->hw_start + range->io_start;
}
